#!/usr/bin/env python
# coding=utf-8
# Compatible with Python 2
import sys, io, gzip
from wsgiref import handlers
from platform import python_implementation
if sys.version_info > (3,):
	from http import server
	from urllib import parse
	import socketserver
else:
	import BaseHTTPServer as server
	import urlparse as parse
	import urllib
	parse.quote = lambda s: urllib.quote(s.encode('utf-8'))
	parse.unquote = urllib.unquote
	parse.unquote_to_bytes = urllib.unquote
	import SocketServer as socketserver

__version__ = '0.1'
server_version = 'WSGID/' + __version__
sys_version = python_implementation() + '/' + sys.version.split()[0]
software_version = server_version + ' ' + sys_version

class ServerException(Exception): pass

class ChunkedBuffer:
	bufsize = 8192
	_write = None
	chunked = False
	def __init__(self, init_flush):
		self.init_flush = init_flush
		self.first_flush = True
		self.buffer = io.BytesIO()
		self.clear()
		self.stream = self.buffer	# can be wrapped
		self.content_encoding = 'deflate'
	def clear(self):
		self.buffer.seek(0)
		self.buffer.truncate(0)
		self.bytes_sent = 0
		self.more = True
		self.error = None
	def wrap_gzip(self, compressLevel=6):
		self.stream = gzip.open(self.buffer, 'wb', compressLevel)
		self.content_encoding = 'gzip'
	def write(self, data):
		if self.error: return
		l = self.stream.write(data)
		self.stream.flush()
		if self.length() >= self.bufsize:
			self.more = True
			self.flush()
	def send(self, data):
		if self.error: return
		try:
			self._write(data)
		except Exception as e:
			self.error = e
		else:
			self.bytes_sent += len(data)
	def _flush(self):
		l = self.length()
		if l:
			if self.chunked:
				self.send(('%x\r\n' % l).encode())
			self.send(self.buffer.getvalue())
			if self.chunked:
				self.send(b'\r\n')
			self.buffer.truncate(0)
			self.buffer.seek(0)
	def flush(self):
		if self.first_flush:
			self.first_flush = False
			self.init_flush()
		self._flush()
	def close(self):
		self.more = False
		self.flush()
		if self.chunked:
			self.send(b'0\r\n\r\n')
	def length(self):
		return self.buffer.tell()

class WSGIHandler(handlers.SimpleHandler):
	server_software = software_version
	http_version = '1.1'
	chunked = False
	encoding = 'utf-8'
	def start_response(self, *k, **kw):
		write = handlers.SimpleHandler.start_response(self, *k, **kw)
		self.buffer =  ChunkedBuffer(self.send_headers)
		if self.headers.get('Content-Encoding') == 'gzip':
			self.buffer.wrap_gzip()
		return write
	def cleanup_headers(self):
		if self.environ['SERVER_PROTOCOL'] >= 'HTTP/1.1':
			self.http_version = '1.1'
			self.wsgi_run_once = self.environ.get('HTTP_CONNECTION', 'keep-alive') != 'keep-alive'
			self.chunked = True	# only allowed in HTTP/1.1
			if 'Content-Length' in self.headers:
				if self.buffer.content_encoding == 'deflate':
					self.chunked = False
				else:
					del self.headers['Content-Length']
		else:
			self.http_version = '1.0'
			self.wsgi_run_once = self.environ.get('HTTP_CONNECTION', 'close') != 'keep-alive'
			self.chunked = False
		if not self.buffer.more:
			self.chunked = False
			self.headers['Content-Length'] = str(self.buffer.length())
		elif self.chunked:
			self.headers['Transfer-Encoding'] = 'chunked'
		else:
			self.wsgi_run_once = True
		self.headers['Connection'] = 'close' if self.wsgi_run_once else 'keep-alive'
	def send_headers(self):
		self.cleanup_headers()
		if not self.origin_server or self.client_is_modern():
			self.send_preamble()
			self._write(bytes(self.headers))
		self.headers_sent = True
		self.buffer._write = self._write
		self.buffer.chunked = self.chunked
	def write(self, data):
		if not isinstance(data, bytes):
			data = data.encode(self.encoding)
		if not self.status:
			self.start_response('200 OK', [])
			#raise ServerException('write() before start_response()')
		self.buffer.write(data)
	def finish_content(self):
		self.buffer.close()
	def close(self):
		try:
			self.request_handler.log_request(
				self.status.split(' ', 1)[0], self.buffer.bytes_sent
			)
		finally:
			handlers.SimpleHandler.close(self)

class HTTPHandler(server.BaseHTTPRequestHandler):
	timeout = 3
	server_version = server_version
	def get_environ(self):
		env = self.server.base_environ.copy()
		env['SERVER_PROTOCOL'] = self.request_version
		env['SERVER_SOFTWARE'] = self.server_version
		env['REQUEST_METHOD'] = self.command
		env['REQUEST_URI'] = self.path
		if '?' in self.path:
			path,query = self.path.split('?',1)
		else:
			path,query = self.path,''

		env['PATH_INFO'] = parse.unquote_to_bytes(path).decode('iso-8859-1')
		env['QUERY_STRING'] = query

		host = self.address_string()
		if host != self.client_address[0]:
			env['REMOTE_HOST'] = host
		env['REMOTE_ADDR'] = self.client_address[0]

		if self.headers.get('content-type') is None:
			#env['CONTENT_TYPE'] = self.headers.get_content_type()
			env['CONTENT_TYPE'] = 'text/plain'
		else:
			env['CONTENT_TYPE'] = self.headers['content-type']

		length = self.headers.get('content-length')
		if length:
			env['CONTENT_LENGTH'] = length

		for k, v in self.headers.items():
			k = k.replace('-', '_').upper()
			v = v.strip()
			if k in env:
				continue              # skip content length, type,etc.
			k = 'HTTP_' + k
			if k in env:
				env[k] += ',' + v     # comma-separate multiple headers
			else:
				env[k] = v
		return env

	def get_stderr(self):
		return sys.stderr

	def handle_one_request(self):
		"""Handle a single HTTP request"""
		try:
			self.raw_requestline = self.rfile.readline()
			if not self.parse_request(): return
			handler = WSGIHandler(
				self.rfile, self.wfile, self.get_stderr(), self.get_environ()
			)
			handler.request_handler = self      # backpointer for logging
			handler.run(self.server.get_app())
			self.close_connection = 1 if handler.wsgi_run_once else 0
		except:
			self.close_connection = 1

class HTTPServer(socketserver.ThreadingMixIn, server.HTTPServer):
	application = None
	def server_bind(self):
		server.HTTPServer.server_bind(self)
		env = self.base_environ = {}
		env['SERVER_NAME'] = self.server_name
		env['GATEWAY_INTERFACE'] = 'CGI/1.1'
		env['SERVER_PORT'] = str(self.server_port)
		env['REMOTE_HOST'] = ''
		env['CONTENT_LENGTH'] = ''
		env['SCRIPT_NAME'] = ''
	def get_app(self):
		return self.application
	def set_app(self, application):
		self.application = application
